// This is a generated file. Use and modify at your own risk.
////////////////////////////////////////////////////////////////////////////////

/*******************************************************************************
Vendor: Xilinx
Associated Filename: main.c
#Purpose: This example shows a basic vector add +1 (constant) by manipulating
#         memory inplace.
*******************************************************************************/

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <unistd.h>
#include <assert.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <CL/opencl.h>
#include <CL/cl_ext.h>
#include <bits/stdc++.h>
#include "xclhal2.h"

////////////////////////////////////////////////////////////////////////////////

#define NUM_WORKGROUPS (1)
#define WORKGROUP_SIZE (256)

#if defined(SDX_PLATFORM) && !defined(TARGET_DEVICE)
#define STR_VALUE(arg)      #arg
#define GET_STRING(name) STR_VALUE(name)
#define TARGET_DEVICE GET_STRING(SDX_PLATFORM)
#endif

#define FREQ 163

using namespace std;

////////////////////////////////////////////////////////////////////////////////
float k_power (int power) {
	float result = 1.0;
	//cout << power << endl;
	if (power >= 0) {
		result = 1 << power;
	} else {
		for (int i = power; i < 0; i ++) {
			result /= 2.0;
		}
	}
	return result;
}
float float16_to_float32 (int a) {
	// a: 0x0000ffff
	float result = 0.0;
	int ratio = ((a >> 10) & 0x0000001f) - 15;

	result += 1.0 * k_power(ratio);
	for (int i = 0; i < 10; i ++) {
		result += ((a >> i) & 0x00000001) * k_power(ratio - 10 + i);
	}

	return result;
}
//------------------alloc aligned memory---------------------
void *allocate_aligned(size_t size, size_t alignment)
{
   const size_t mask = alignment - 1;
   const uintptr_t mem = (uintptr_t) calloc(size + alignment, 1);
    return (void *) ((mem + mask) & ~mask);
}

int load_file_to_memory(const char *filename, char **result)
{
    uint size = 0;
    FILE *f = fopen(filename, "rb");
    if (f == NULL) {
        *result = NULL;
        return -1; // -1 means file opening fail
    }
    fseek(f, 0, SEEK_END);
    size = ftell(f);
    fseek(f, 0, SEEK_SET);
    *result = (char *)malloc(size+1);
    if (size != fread(*result, sizeof(char), size, f)) {
        free(*result);
        return -2; // -2 means file reading fail
    }
    fclose(f);
    (*result)[size] = 0;
    return size;
}

int main(int argc, char** argv)
{
    cl_int err;                            // error code returned from api calls
    int check_status = 0;
    // const uint number_of_words = 4096; // 16KB of data


    cl_platform_id platform_id;         // platform id
    cl_device_id device_id;             // compute device id
    cl_context context;                 // compute context
    cl_command_queue commands;          // compute command queue
    cl_program program;                 // compute programs
    cl_kernel kernel;                   // compute kernel

    char cl_platform_vendor[1001];
    char target_device_name[1001] = TARGET_DEVICE;

    FILE *offset_c1_fp, *offset_c2_fp, *info_c1_fp, *info_c2_fp, *degree_c1_file, *degree_c2_file; // data in
    FILE *offset_fp, *info_fp;  // vertex number and edge number
    FILE *bfs_result_fp, *cycle_fp;    // bfs result

    int single_channel_vertex, single_channel_edge;
    int vertex_num, edge_num, iterator_num;
    int *h_A_1_input, *h_A_2_input, *h_B_1_input, *h_B_2_input;
    int *h_C_output, *h_D_1_output, *h_D_2_output, *h_D_3_output, *h_D_4_output;
    int h_E_output;
    int *h_F_1_input, *h_F_2_input;

    cl_mem d_A_1;                         // device memory used for a vector
    cl_mem d_A_2;                         // device memory used for a vector
    cl_mem d_B_1;                         // device memory used for a vector
    cl_mem d_B_2;                         // device memory used for a vector
    cl_mem d_C;
    cl_mem d_D_1;
    cl_mem d_D_2;
    cl_mem d_D_3;
    cl_mem d_D_4;
    cl_mem d_E;
    cl_mem d_F_1;
    cl_mem d_F_2;

    if (argc != 13) {
        printf("Usage: %s xclbin offset_file info_file offset_c1_file offset_c2_file info_c1_file info_c2_file bfs_result_file cycle_file, iterator_num, degree_c1_file, degree_c2_file\n", argv[0]);
        return EXIT_FAILURE;
    }
    iterator_num = atoi (argv[10]);
    if (iterator_num < 1 || iterator_num > 100) {
        cout << "[ERROR] Invalid Iterator number = " << iterator_num << endl;
        exit (-1);
    }
    cout << "[INFO] Iterator_num = " << iterator_num << endl;

    /////////////////////////////////////////////////////////////////////////////////////////////////
    // set vertex number and edge number
    if ((offset_fp = fopen(argv[2], "r")) == NULL) {
        cout << "[ERROR] Failed to open file " << argv[2] << endl;
        exit (-1);
    }
    fscanf (offset_fp, "%d", &vertex_num);
    cout << "[INFO] Open file " << argv[2] << ", vertex number set to " << vertex_num << endl;
    fclose (offset_fp);

    if ((info_fp = fopen(argv[3], "r")) == NULL) {
        cout << "[ERROR] Failed to open file " << argv[3] << endl;
        exit (-1);
    }
    fscanf (info_fp, "%d", &edge_num);
    cout << "[INFO] Open file " << argv[3] << ", edge number set to " << edge_num << endl;
    fclose (info_fp);
    ////////////////////////////////////////////////////////////////////////////////////////////////

    // calculate channel write number
    single_channel_vertex = (vertex_num + 1) / 32 * 16 + ((vertex_num + 1) % 32 > 0 ? 16 : 0);
    single_channel_edge   = (edge_num + 1) / 32 * 16 + ((edge_num + 1) % 32 > 0 ? 16 : 0);

    h_A_1_input = (int *) calloc (single_channel_vertex, sizeof(int));
    h_A_2_input = (int *) calloc (single_channel_vertex, sizeof(int));

    h_B_1_input = (int *) calloc (single_channel_edge, sizeof(int));
    h_B_2_input = (int *) calloc (single_channel_edge, sizeof(int));

    h_F_1_input = (int *) calloc (single_channel_edge, sizeof(int));
    h_F_2_input = (int *) calloc (single_channel_edge, sizeof(int));

    h_C_output = (int *) calloc (vertex_num + 64, sizeof(int));

    h_D_1_output = (int *) calloc (vertex_num + 64, sizeof(int));
    h_D_2_output = (int *) calloc (vertex_num + 64, sizeof(int));
    h_D_3_output = (int *) calloc (vertex_num + 64, sizeof(int));
    h_D_4_output = (int *) calloc (vertex_num + 64, sizeof(int));

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // open offset and info file
    if ((offset_c1_fp = fopen(argv[4], "r")) == NULL) {
        cout << "[ERROR] Failed to open file " << argv[4] << endl;
        exit (-1);
    }
    cout << "[INFO] Open file " << argv[4] << endl;
    if ((offset_c2_fp = fopen(argv[5], "r")) == NULL) {
        cout << "[ERROR] Failed to open file " << argv[5] << endl;
        exit (-1);
    }
    cout << "[INFO] Open file " << argv[5] << endl;
    if ((info_c1_fp = fopen(argv[6], "r")) == NULL) {
        cout << "[ERROR] Failed to open file " << argv[6] << endl;
        exit (-1);
    }
    cout << "[INFO] Open file " << argv[6] << endl;
    if ((info_c2_fp = fopen(argv[7], "r")) == NULL) {
        cout << "[ERROR] Failed to open file " << argv[7] << endl;
        exit (-1);
    }
    cout << "[INFO] Open file " << argv[7] << endl;
    if ((degree_c1_file = fopen(argv[11], "r")) == NULL) {
        cout << "[ERROR] Failed to open file " << argv[11] << endl;
        exit (-1);
    }
    cout << "[INFO] Open file " << argv[11] << endl;
    if ((degree_c2_file = fopen(argv[12], "r")) == NULL) {
        cout << "[ERROR] Failed to open file " << argv[12] << endl;
        exit (-1);
    }
    cout << "[INFO] Open file " << argv[12] << endl;
    ////////////////////////////////////////////////////////////////////////////////////////////////

    ////////////////////////////////////////////////////////////////////////////////////////////////
    // open result file
    if ((bfs_result_fp = fopen(argv[8], "w")) == NULL) {
        cout << "[ERROR] Failed to open file " << argv[8] << endl;
        exit (-1);
    }
    cout << "[INFO] Open file " << argv[8] << endl;
    if ((cycle_fp = fopen(argv[9], "w")) == NULL) {
        cout << "[ERROR] Failed to open file " << argv[9] << endl;
        exit (-1);
    }
    cout << "[INFO] Open file " << argv[9] << endl;
    ////////////////////////////////////////////////////////////////////////////////////////////////

    cout << "single_channel_vertex = " << single_channel_vertex << ", single_channel_edge = " << single_channel_edge << endl;

    cout << "[INFO] Read offset file." << endl;
    for (int i = 0; i < single_channel_vertex; i ++) {
        fscanf (offset_c1_fp, "%d", &(h_A_1_input[i]));
        fscanf (offset_c2_fp, "%d", &(h_A_2_input[i]));
    }
    cout << "[INFO] Read offset file complete." << endl;
    cout << "[INFO] Read info file." << endl;
    for (int i = 0; i < single_channel_edge; i ++) {
        fscanf (info_c1_fp, "%d", &(h_B_1_input[i]));
        fscanf (info_c2_fp, "%d", &(h_B_2_input[i]));
    }
    cout << "[INFO] Read info file complete." << endl;
    cout << "[INFO] Read degree file." << endl;
    for (int i = 0; i < single_channel_edge; i ++) {
        fscanf (degree_c1_file, "%d", &(h_F_1_input[i]));
        fscanf (degree_c2_file, "%d", &(h_F_2_input[i]));
    }
    cout << "[INFO] Read degree file complete." << endl;
    for (int i = 0; i < vertex_num + 64; i ++) {
        h_C_output[i]   = i;
        h_D_1_output[i] = i;
        h_D_2_output[i] = i;
        h_D_3_output[i] = i;
        h_D_4_output[i] = i;
    }
    h_E_output = 0;

    fclose (offset_c1_fp);
    fclose (offset_c2_fp);
    fclose (info_c1_fp);
    fclose (info_c2_fp);
    fclose (degree_c1_file);
    fclose (degree_c2_file);

    printf ("h_F_1_input: ");
    for (int i = 0; i < 10; i ++) {
    	printf ("%d ", h_F_1_input[i]);
    }
    printf ("\n");
    printf ("h_F_2_input: ");
    for (int i = 0; i < 10; i ++) {
    	printf ("%d ", h_F_2_input[i]);
    }
    printf ("\n");

   // Get all platforms and then select Xilinx platform
    cl_platform_id platforms[16];       // platform id
    cl_uint platform_count;
    int platform_found = 0;
    err = clGetPlatformIDs(16, platforms, &platform_count);
    if (err != CL_SUCCESS) {
        printf("Error: Failed to find an OpenCL platform!\n");
        printf("Test failed\n");
        return EXIT_FAILURE;
    }
    printf("INFO: Found %d platforms\n", platform_count);

    // Find Xilinx Plaftorm
    for (unsigned int iplat=0; iplat<platform_count; iplat++) {
        err = clGetPlatformInfo(platforms[iplat], CL_PLATFORM_VENDOR, 1000, (void *)cl_platform_vendor,NULL);
        if (err != CL_SUCCESS) {
            printf("Error: clGetPlatformInfo(CL_PLATFORM_VENDOR) failed!\n");
            printf("Test failed\n");
            return EXIT_FAILURE;
        }
        if (strcmp(cl_platform_vendor, "Xilinx") == 0) {
            printf("INFO: Selected platform %d from %s\n", iplat, cl_platform_vendor);
            platform_id = platforms[iplat];
            platform_found = 1;
        }
    }
    if (!platform_found) {
        printf("ERROR: Platform Xilinx not found. Exit.\n");
        return EXIT_FAILURE;
    }

   // Get Accelerator compute device
    cl_uint num_devices;
    unsigned int device_found = 0;
    cl_device_id devices[16];  // compute device id
    char cl_device_name[1001];
    err = clGetDeviceIDs(platform_id, CL_DEVICE_TYPE_ACCELERATOR, 16, devices, &num_devices);
    printf("INFO: Found %d devices\n", num_devices);
    if (err != CL_SUCCESS) {
        printf("ERROR: Failed to create a device group!\n");
        printf("ERROR: Test failed\n");
        return -1;
    }

    //iterate all devices to select the target device.
    for (uint i=0; i<num_devices; i++) {
        err = clGetDeviceInfo(devices[i], CL_DEVICE_NAME, 1024, cl_device_name, 0);
        if (err != CL_SUCCESS) {
            printf("Error: Failed to get device name for device %d!\n", i);
            printf("Test failed\n");
            return EXIT_FAILURE;
        }
        printf("CL_DEVICE_NAME %s\n", cl_device_name);
        if(strcmp(cl_device_name, target_device_name) == 0) {
            device_id = devices[i];
            device_found = 1;
            printf("Selected %s as the target device\n", cl_device_name);
       }
    }

    if (!device_found) {
        printf("Target device %s not found. Exit.\n", target_device_name);
        return EXIT_FAILURE;
    }

    // Create a compute context
    //
    context = clCreateContext(0, 1, &device_id, NULL, NULL, &err);
    if (!context) {
        printf("Error: Failed to create a compute context!\n");
        printf("Test failed\n");
        return EXIT_FAILURE;
    }

    // Create a command commands
    commands = clCreateCommandQueue(context, device_id, 0, &err);
    if (!commands) {
        printf("Error: Failed to create a command commands!\n");
        printf("Error: code %i\n",err);
        printf("Test failed\n");
        return EXIT_FAILURE;
    }

    int status;

    // Create Program Objects
    // Load binary from disk
    unsigned char *kernelbinary;
    char *xclbin = argv[1];

    //------------------------------------------------------------------------------
    // xclbin
    //------------------------------------------------------------------------------
    printf("INFO: loading xclbin %s\n", xclbin);
    int n_i0 = load_file_to_memory(xclbin, (char **) &kernelbinary);
    if (n_i0 < 0) {
        printf("failed to load kernel from xclbin: %s\n", xclbin);
        printf("Test failed\n");
        return EXIT_FAILURE;
    }

    size_t n0 = n_i0;

    // Create the compute program from offline
    program = clCreateProgramWithBinary(context, 1, &device_id, &n0,
                                        (const unsigned char **) &kernelbinary, &status, &err);

    if ((!program) || (err!=CL_SUCCESS)) {
        printf("Error: Failed to create compute program from binary %d!\n", err);
        printf("Test failed\n");
        return EXIT_FAILURE;
    }

    // Build the program executable
    //
    err = clBuildProgram(program, 0, NULL, NULL, NULL, NULL);
    if (err != CL_SUCCESS) {
        size_t len;
        char buffer[2048];

        printf("Error: Failed to build program executable!\n");
        clGetProgramBuildInfo(program, device_id, CL_PROGRAM_BUILD_LOG, sizeof(buffer), buffer, &len);
        printf("%s\n", buffer);
        printf("Test failed\n");
        return EXIT_FAILURE;
    }

    // Create the compute kernel in the program we wish to run
    //
    kernel = clCreateKernel(program, "Accelerator_Final_PR", &err);
    if (!kernel || err != CL_SUCCESS) {
        printf("Error: Failed to create compute kernel!\n");
        printf("Test failed\n");
        return EXIT_FAILURE;
    }

    // Create structs to define memory bank mapping
    cl_mem_ext_ptr_t mem_ext[14];
    mem_ext[0].obj = NULL;
    mem_ext[0].param = 0;
    mem_ext[1].obj = NULL;
    mem_ext[1].param = 0;
    mem_ext[2].obj = NULL;
    mem_ext[2].param = 0;
    mem_ext[3].obj = NULL;
    mem_ext[3].param = 0;
    mem_ext[4].obj = NULL;
    mem_ext[4].param = 0;
    mem_ext[5].obj = NULL;
    mem_ext[5].param = 0;
    mem_ext[6].obj = NULL;
    mem_ext[6].param = 0;
    mem_ext[7].obj = NULL;
    mem_ext[7].param = 0;
    mem_ext[8].obj = NULL;
    mem_ext[8].param = 0;
    mem_ext[9].obj = NULL;
    mem_ext[9].param = 0;
    mem_ext[10].obj = NULL;
    mem_ext[10].param = 0;
    mem_ext[11].obj = NULL;
    mem_ext[11].param = 0;

    mem_ext[0].flags = XCL_MEM_DDR_BANK0;
    d_A_1 = clCreateBuffer(context,  CL_MEM_READ_WRITE | CL_MEM_EXT_PTR_XILINX,  sizeof(int) * single_channel_vertex, &mem_ext[0], NULL);

    mem_ext[1].flags = XCL_MEM_DDR_BANK1;
    d_A_2 = clCreateBuffer(context,  CL_MEM_READ_WRITE | CL_MEM_EXT_PTR_XILINX,  sizeof(int) * single_channel_vertex, &mem_ext[1], NULL);

    mem_ext[2].flags = XCL_MEM_DDR_BANK2;
    d_B_1 = clCreateBuffer(context,  CL_MEM_READ_WRITE | CL_MEM_EXT_PTR_XILINX,  sizeof(int) * single_channel_edge, &mem_ext[2], NULL);

    mem_ext[3].flags = XCL_MEM_DDR_BANK3;
    d_B_2 = clCreateBuffer(context,  CL_MEM_READ_WRITE | CL_MEM_EXT_PTR_XILINX,  sizeof(int) * single_channel_edge, &mem_ext[3], NULL);

    mem_ext[4].flags = XCL_MEM_DDR_BANK0;
    d_C = clCreateBuffer(context,  CL_MEM_READ_WRITE | CL_MEM_EXT_PTR_XILINX,  sizeof(int) * (vertex_num + 64), &mem_ext[4], NULL);

    mem_ext[5].flags = XCL_MEM_DDR_BANK0;
    d_D_1 = clCreateBuffer(context,  CL_MEM_READ_WRITE | CL_MEM_EXT_PTR_XILINX,  sizeof(int) * (vertex_num + 64), &mem_ext[5], NULL);

    mem_ext[6].flags = XCL_MEM_DDR_BANK0;
    d_D_2 = clCreateBuffer(context,  CL_MEM_READ_WRITE | CL_MEM_EXT_PTR_XILINX,  sizeof(int) * (vertex_num + 64), &mem_ext[6], NULL);

    mem_ext[7].flags = XCL_MEM_DDR_BANK1;
    d_D_3 = clCreateBuffer(context,  CL_MEM_READ_WRITE | CL_MEM_EXT_PTR_XILINX,  sizeof(int) * (vertex_num + 64), &mem_ext[7], NULL);

    mem_ext[8].flags = XCL_MEM_DDR_BANK1;
    d_D_4 = clCreateBuffer(context,  CL_MEM_READ_WRITE | CL_MEM_EXT_PTR_XILINX,  sizeof(int) * (vertex_num + 64), &mem_ext[8], NULL);

    mem_ext[9].flags = XCL_MEM_DDR_BANK1;
    d_E = clCreateBuffer(context,  CL_MEM_READ_WRITE | CL_MEM_EXT_PTR_XILINX,  sizeof(int), &mem_ext[9], NULL);

    mem_ext[10].flags = XCL_MEM_DDR_BANK0;
    d_F_1 = clCreateBuffer(context,  CL_MEM_READ_WRITE | CL_MEM_EXT_PTR_XILINX,  sizeof(int) * single_channel_edge, &mem_ext[10], NULL);

    mem_ext[11].flags = XCL_MEM_DDR_BANK1;
    d_F_2 = clCreateBuffer(context,  CL_MEM_READ_WRITE | CL_MEM_EXT_PTR_XILINX,  sizeof(int) * single_channel_edge, &mem_ext[11], NULL);

    if (!(d_A_1 && d_A_2 && d_B_1 && d_B_2 && d_C && d_D_1 && d_D_2 && d_D_3 && d_D_4 && d_E && d_F_1 && d_F_2)) {
        printf("Error: Failed to allocate device memory!\n");
        printf("Test failed\n");
        return EXIT_FAILURE;
    }


    err = clEnqueueWriteBuffer(commands, d_A_1, CL_TRUE, 0, sizeof(int) * single_channel_vertex, h_A_1_input, 0, NULL, NULL);
    if (err != CL_SUCCESS) {
        printf("Error: Failed to write to source array h_A_input!\n");
        printf("Test failed\n");
        return EXIT_FAILURE;
    }
    err = clEnqueueWriteBuffer(commands, d_A_2, CL_TRUE, 0, sizeof(int) * single_channel_vertex, h_A_2_input, 0, NULL, NULL);
    if (err != CL_SUCCESS) {
        printf("Error: Failed to write to source array h_A_input!\n");
        printf("Test failed\n");
        return EXIT_FAILURE;
    }

    err = clEnqueueWriteBuffer(commands, d_B_1, CL_TRUE, 0, sizeof(int) * single_channel_edge, h_B_1_input, 0, NULL, NULL);
    if (err != CL_SUCCESS) {
        printf("Error: Failed to write to source array h_B_input!\n");
        printf("Test failed\n");
        return EXIT_FAILURE;
    }
    err = clEnqueueWriteBuffer(commands, d_B_2, CL_TRUE, 0, sizeof(int) * single_channel_edge, h_B_2_input, 0, NULL, NULL);
    if (err != CL_SUCCESS) {
        printf("Error: Failed to write to source array h_B_input!\n");
        printf("Test failed\n");
        return EXIT_FAILURE;
    }

    err = clEnqueueWriteBuffer(commands, d_F_1, CL_TRUE, 0, sizeof(int) * single_channel_edge, h_F_1_input, 0, NULL, NULL);
    if (err != CL_SUCCESS) {
        printf("Error: Failed to write to source array h_F_input!\n");
        printf("Test failed\n");
        return EXIT_FAILURE;
   }

    err = clEnqueueWriteBuffer(commands, d_F_2, CL_TRUE, 0, sizeof(int) * single_channel_edge, h_F_2_input, 0, NULL, NULL);
    if (err != CL_SUCCESS) {
        printf("Error: Failed to write to source array h_F_input!\n");
        printf("Test failed\n");
        return EXIT_FAILURE;
    }

    // Set the arguments to our compute kernel
    // int vector_length = MAX_LENGTH;
    err = 0;
    cl_uint d_iterator_max = iterator_num;
    cl_uint cl_vertex_num = vertex_num;
    cl_uint cl_edge_num = edge_num;
    err |= clSetKernelArg(kernel, 0, sizeof(cl_uint), &d_iterator_max); // Not used in example RTL logic.
    err |= clSetKernelArg(kernel, 1, sizeof(cl_uint), &cl_vertex_num); // Not used in example RTL logic.
    err |= clSetKernelArg(kernel, 2, sizeof(cl_uint), &cl_edge_num); // Not used in example RTL logic.
    err |= clSetKernelArg(kernel, 3, sizeof(cl_mem), &d_A_1); 
    err |= clSetKernelArg(kernel, 4, sizeof(cl_mem), &d_A_2); 
    err |= clSetKernelArg(kernel, 5, sizeof(cl_mem), &d_B_1); 
    err |= clSetKernelArg(kernel, 6, sizeof(cl_mem), &d_B_2); 
    err |= clSetKernelArg(kernel, 7, sizeof(cl_mem), &d_C); 
    err |= clSetKernelArg(kernel, 8, sizeof(cl_mem), &d_D_1); 
    err |= clSetKernelArg(kernel, 9, sizeof(cl_mem), &d_D_2); 
    err |= clSetKernelArg(kernel, 10, sizeof(cl_mem), &d_D_3); 
    err |= clSetKernelArg(kernel, 11, sizeof(cl_mem), &d_D_4); 
    err |= clSetKernelArg(kernel, 12, sizeof(cl_mem), &d_E); 
    err |= clSetKernelArg(kernel, 13, sizeof(cl_mem), &d_F_1); 
    err |= clSetKernelArg(kernel, 14, sizeof(cl_mem), &d_F_2); 

    if (err != CL_SUCCESS) {
        printf("Error: Failed to set kernel arguments! %d\n", err);
        printf("Test failed\n");
        return EXIT_FAILURE;
    }

    // Execute the kernel over the entire range of our 1d input data set
    // using the maximum number of work group items for this device

    err = clEnqueueTask(commands, kernel, 0, NULL, NULL);
    if (err) {
            printf("Error: Failed to execute kernel! %d\n", err);
            printf("Test failed\n");
            return EXIT_FAILURE;
        }

    // Read back the results from the device to verify the output
    //
    // cl_event readevent1;
    cl_event readevent;
    clFinish(commands);

    err = 0;
    err |= clEnqueueReadBuffer( commands, d_A_1, CL_TRUE, 0, sizeof(int) * (single_channel_vertex), h_A_1_input, 0, NULL, &readevent );
    err |= clEnqueueReadBuffer( commands, d_A_2, CL_TRUE, 0, sizeof(int) * (single_channel_vertex), h_A_2_input, 0, NULL, &readevent );
    err |= clEnqueueReadBuffer( commands, d_B_1, CL_TRUE, 0, sizeof(int) * (single_channel_edge), h_B_1_input, 0, NULL, &readevent );
    err |= clEnqueueReadBuffer( commands, d_B_2, CL_TRUE, 0, sizeof(int) * (single_channel_edge), h_B_2_input, 0, NULL, &readevent );
    err |= clEnqueueReadBuffer( commands, d_F_1, CL_TRUE, 0, sizeof(int) * (single_channel_edge), h_F_1_input, 0, NULL, &readevent );
    err |= clEnqueueReadBuffer( commands, d_F_2, CL_TRUE, 0, sizeof(int) * (single_channel_edge), h_F_2_input, 0, NULL, &readevent );

    err |= clEnqueueReadBuffer( commands, d_C, CL_TRUE, 0, sizeof(int) * (vertex_num + 64), h_C_output, 0, NULL, &readevent );
    err |= clEnqueueReadBuffer( commands, d_D_1, CL_TRUE, 0, sizeof(int) * (vertex_num + 64), h_D_1_output, 0, NULL, &readevent );
    err |= clEnqueueReadBuffer( commands, d_D_2, CL_TRUE, 0, sizeof(int) * (vertex_num + 64), h_D_2_output, 0, NULL, &readevent );
    err |= clEnqueueReadBuffer( commands, d_D_3, CL_TRUE, 0, sizeof(int) * (vertex_num + 64), h_D_3_output, 0, NULL, &readevent );
    err |= clEnqueueReadBuffer( commands, d_D_4, CL_TRUE, 0, sizeof(int) * (vertex_num + 64), h_D_4_output, 0, NULL, &readevent );
    err |= clEnqueueReadBuffer( commands, d_E, CL_TRUE, 0, sizeof(int), &h_E_output, 0, NULL, &readevent );


    if (err != CL_SUCCESS) {
            printf("Error: Failed to read output array! %d\n", err);
            printf("Test failed\n");
            return EXIT_FAILURE;
        }
    clWaitForEvents(1, &readevent);

    //--------------------------------------------------------------------------
    // Shutdown and cleanup
    //-------------------------------------------------------------------------- 
    clReleaseMemObject(d_A_1);
    clReleaseMemObject(d_A_2);

    clReleaseMemObject(d_B_1);
    clReleaseMemObject(d_B_2);

    clReleaseMemObject(d_F_1);
    clReleaseMemObject(d_F_2);

    clReleaseMemObject(d_C);

    clReleaseMemObject(d_D_1);
    clReleaseMemObject(d_D_2);
    clReleaseMemObject(d_D_3);
    clReleaseMemObject(d_D_4);

    clReleaseMemObject(d_E);

    clReleaseProgram(program);
    clReleaseKernel(kernel);
    clReleaseCommandQueue(commands);
    clReleaseContext(context);
    clReleaseDevice(device_id);

    printf ("h_A_1_input: ");
    for (int i = 0; i < 10; i ++) {
    	printf ("%d ", h_A_1_input[i]);
    }
    printf ("\n");
    printf ("h_A_2_input: ");
    for (int i = 0; i < 10; i ++) {
    	printf ("%d ", h_A_2_input[i]);
    }
    printf ("\n");

    printf ("h_B_1_input: ");
    for (int i = 0; i < 10; i ++) {
    	printf ("%d ", h_B_1_input[i]);
    }
    printf ("\n");
    printf ("h_B_2_input: ");
    for (int i = 0; i < 10; i ++) {
    	printf ("%d ", h_B_2_input[i]);
    }
    printf ("\n");

    printf ("h_F_1_input: ");
    for (int i = 0; i < 10; i ++) {
    	printf ("%d ", h_F_1_input[i]);
    }
    printf ("\n");
    printf ("h_F_2_input: ");
    for (int i = 0; i < 10; i ++) {
    	printf ("%d ", h_F_2_input[i]);
    }
    printf ("\n");

    printf ("\n");
    printf ("h_C_output: ");
    for (int i = 0; i < 10; i ++) {
    	printf ("%x ", h_C_output[i]);
    }
    printf ("\n");
    printf ("h_D_1_output: ");
    for (int i = 0; i < 10; i ++) {
    	printf ("%d ", h_D_1_output[i]);
    }
    printf ("\n");
    printf ("h_D_2_output: ");
    for (int i = 0; i < 10; i ++) {
    	printf ("%d ", h_D_2_output[i]);
    }
    printf ("\n");
    printf ("h_E_output: %d", h_E_output);
    printf ("\n");

    for (int i = 0; i < vertex_num / 4 + 16; i += 1) {
        int addr1 = ((h_D_4_output[i] & 0x000000ff) << 24) | ((h_D_3_output[i] & 0x000000ff) << 16) | ((h_D_2_output[i] & 0x000000ff) << 8)     | ((h_D_1_output[i] & 0x000000ff));
        int addr2 = ((h_D_4_output[i] & 0x0000ff00) << 16) | ((h_D_3_output[i] & 0x0000ff00) << 8)  | (h_D_2_output[i] & 0x0000ff00)            | ((h_D_1_output[i] & 0x0000ff00) >> 8);
        int addr3 = ((h_D_4_output[i] & 0x00ff0000) << 8)  | (h_D_3_output[i] & 0x00ff0000)         | ((h_D_2_output[i] & 0x00ff0000) >> 8)     | ((h_D_1_output[i] & 0x00ff0000) >> 16);
        int addr4 = ((h_D_4_output[i] & 0xff000000)        | ((unsigned)(h_D_3_output[i] & 0xff000000) >> 8) | ((unsigned)(h_D_2_output[i] & 0xff000000) >> 16)   | ((unsigned)(h_D_1_output[i] & 0xff000000) >> 24));
        int data1 = h_C_output[2 * i] & 0x0000ffff;
        int data2 = (h_C_output[2 * i] >> 16) & 0x0000ffff;
        int data3 = (h_C_output[2 * i + 1]) & 0x0000ffff;
        int data4 = (h_C_output[2 * i + 1] >> 16) & 0x0000ffff;


        fprintf (bfs_result_fp, "%d %f\n", addr1, float16_to_float32(data1));
        fprintf (bfs_result_fp, "%d %f\n", addr2, float16_to_float32(data2));
        fprintf (bfs_result_fp, "%d %f\n", addr3, float16_to_float32(data3));
        fprintf (bfs_result_fp, "%d %f\n", addr4, float16_to_float32(data4));
    }

    fclose (bfs_result_fp);

    cout << "[INFO] Vertex property is stored in file " << argv[8] << endl;

    cout << "[INFO] Clock Frequency = " << FREQ << endl;
    fprintf (cycle_fp, "frequency = %d\n", FREQ);
    printf ("[INFO]: Input Vertex number is %d.\n", vertex_num);
    fprintf (cycle_fp, "vertex_number = %d\n", vertex_num);
    printf ("[INFO]: Input Edge   number is %d.\n", edge_num);
    fprintf (cycle_fp, "edge_number = %d\n", edge_num);
    printf ("[INFO]: Iterator     number is %d.\n", iterator_num);
    fprintf (cycle_fp, "iterator_number = %d\n", iterator_num);

    printf ("[RESULT]: Total Execution Cycle is %d.\n", h_E_output);
    fprintf (cycle_fp, "total_execution_cycle = %d\n", h_E_output);
    printf ("[RESULT]: Total Execution Time  is %fns.\n", h_E_output * 1000.0 / FREQ);
    fprintf (cycle_fp, "total_execution_time = %f\n", h_E_output * 1000.0 / FREQ);

    printf ("[RESULT]: Single Execution Cycle is %d.\n", h_E_output / iterator_num);
    fprintf (cycle_fp, "total_execution_cycle = %d\n", h_E_output);
    printf ("[RESULT]: Single Execution Time  is %fns.\n", h_E_output * 1000.0 / FREQ / iterator_num);
    fprintf (cycle_fp, "total_execution_time = %f\n", h_E_output * 1000.0 / FREQ / iterator_num);

    printf ("[RESULT]: Single  Execution Performance is %fGTEPs.\n", (edge_num * 1.0) / h_E_output / 1000 * FREQ * iterator_num);
    fprintf (cycle_fp, "performance = %f\n", (edge_num * 1.0) / h_E_output / 1000 * FREQ * iterator_num);

    fclose (cycle_fp);

    if (check_status) {
        printf("INFO: Test failed\n");
        return EXIT_FAILURE;
    } else {
        printf("INFO: Test completed successfully.\n");
        return EXIT_SUCCESS;
    }


} // end of main
